/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

#pragma once

#include "zen_color.h"

#include <memory>
#include <functional>
#include <vector>
#include <cmath>

namespace Zen
{
    class Image
    {
      public:
        static std::shared_ptr<Image> GenerateWithData(EPixel format, size_t width, size_t height, void const* value);

        static std::shared_ptr<Image> GenerateBlank(EPixel format, size_t width, size_t height, uint8_t fill = 0);

      public: // make
        Image() = default;

        void Reset(EPixel format, size_t width, size_t height, uint8_t byte);

        void Reset(EPixel format, size_t width, size_t height, void const* value);

      public: // get
        uint8_t* Data()
        {
            return m_buffer.data();
        }
        uint8_t const* Data() const
        {
            return m_buffer.data();
        }
        size_t Size() const
        {
            return m_buffer.size();
        }
        EPixel PixelType() const
        {
            return m_pixel;
        }
        size_t Width() const
        {
            return m_width;
        }
        size_t Height() const
        {
            return m_height;
        }

      public: // fill
        void FillAll(uint8_t byte);
        void FillAlpha(uint8_t byte);
        void FillGrey(uint8_t byte);
        void FillRGB(Zen::Color3b rgb);
        void FillRGBA(Zen::Color4b rgba);

        /**
		 @ src __zen_must has same stride & larger(>=) height.
		 copy color and alpha.
		 if any self pixel changed, return true.
		 */
        bool FillFrom(EPixel src_bpp, void const* src);
        /**
		 copy alpha only.
		 if no alpha channel, return false.
		 */
        bool FillAlphaFrom(EPixel src_bpp, void const* src);
        /**
		 copy rgb(or grey). only.
		 */
        bool FillColorFrom(EPixel src_bpp, void const* src);

      public:
        void SetColor(size_t x, size_t y, uint8_t byte); // all pixel.
        void SetColor(size_t x, size_t y, uint8_t grey, uint8_t alpha); // GA only.
        void SetColor(size_t x, size_t y, Color3b color); // rgb only.
        void SetColor(size_t x, size_t y, Color4b color); // rgba only.

      protected:
        Image(Image&) = default;

        std::vector<uint8_t> m_buffer;
        size_t m_width = 0;
        size_t m_height = 0;
        EPixel m_pixel = EPixel::None;
    };

    struct ImageDecoder
    {
        virtual std::shared_ptr<Image> decode(std::vector<uint8_t> const& value) = 0;
        virtual ~ImageDecoder() = default;
    };

    struct ImageEncoder
    {
        virtual std::vector<uint8_t> encode(Image const&) = 0;
        virtual ~ImageEncoder() = default;
    };
} // namespace Zen

////////////////////////

namespace Zen
{

    inline std::shared_ptr<Image> Image::GenerateWithData(EPixel format, size_t width, size_t height, void const* value)
    {
        auto image = std::shared_ptr<Image>(new Image());
        image->Reset(format, width, height, value);
        return image;
    }

    inline std::shared_ptr<Image> Image::GenerateBlank(EPixel format, size_t width, size_t height, uint8_t byte)
    {
        auto image = std::shared_ptr<Image>(new Image());
        image->Reset(format, width, height, byte);
        return image;
    }

    inline void Image::Reset(EPixel format, size_t width, size_t height, void const* value)
    {
        this->m_width = width;
        this->m_height = height;
        this->m_pixel = format;
        auto size = (width * height * (int)format);
        if (value)
        {
            auto buf = (const char*)(value);
            this->m_buffer.assign(buf, buf + size);
        }
        else
        {
            this->m_buffer.resize(size, 0);
        }
    }
    inline void Image::Reset(EPixel format, size_t width, size_t height, uint8_t byte)
    {
        this->m_width = width;
        this->m_height = height;
        this->m_pixel = format;
        this->m_buffer.assign(width * height * (int)format, byte);
    }

    inline void Image::FillAll(uint8_t byte)
    {
        ::memset(Data(), byte, Size());
    }

    inline void Image::FillAlpha(uint8_t byte)
    {
        if (m_pixel == EPixel::Grey)
        {
            ::memset(Data(), byte, Size());
        }
        else if (m_pixel == EPixel::GA)
        {
            auto s = Data() + 1;
            auto e = s + Size();
            for (; s < e; s += 2) *s = byte;
        }
        else if (m_pixel == EPixel::RGBA)
        {
            auto s = Data() + 3;
            auto e = s + Size();
            for (; s < e; s += 4) *s = byte;
        }
    }

    inline void Image::FillGrey(uint8_t byte)
    {
        if (m_pixel == EPixel::Grey || m_pixel == EPixel::RGB)
        {
            ::memset(Data(), byte, Size());
        }
        else if (m_pixel == EPixel::GA)
        {
            auto s = Data();
            auto const e = s + Size();
            for (; s < e; s += 2) *s = byte;
        }
        else if (m_pixel == EPixel::RGBA)
        {
            auto s = Data();
            auto const e = s + Size();
            for (; s < e; s += 4) s[0] = s[1] = s[2] = byte;
        }
    }

    inline void Image::FillRGB(Zen::Color3b rgb)
    {
        if (m_pixel == EPixel::Grey || m_pixel == EPixel::GA)
        {
            this->FillGrey(rgb.g8);
        }
        else if (m_pixel == EPixel::RGB || m_pixel == EPixel::RGBA)
        {
            auto s = Data();
            auto const e = s + Size();
            for (; s < e; s += (int)m_pixel)
            {
                s[0] = rgb.r8;
                s[1] = rgb.g8;
                s[2] = rgb.b8;
            }
        }
    }

    inline void Image::FillRGBA(Zen::Color4b rgba)
    {
        if (m_pixel == EPixel::Grey)
        {
            FillAlpha(rgba.a8);
        }
        else if (m_pixel == EPixel::RGB)
        {
            FillRGB(Zen::Color3b(rgba));
        }
        else if (m_pixel == EPixel::GA)
        {
            auto s = Data();
            auto const e = s + Size();
            for (; s < e; s += 2)
            {
                s[0] = rgba.g8;
                s[1] = rgba.a8;
            }
        }
        else if (m_pixel == EPixel::RGBA)
        {
            auto s = Data();
            auto const e = s + Size();
            for (; s < e; s += 4)
            {
                s[0] = rgba.r8;
                s[1] = rgba.g8;
                s[2] = rgba.b8;
                s[3] = rgba.a8;
            }
        }
    }

    inline bool Image::FillFrom(EPixel format, void const* value)
    {
        if (m_pixel == EPixel::None || format == EPixel::None) return false;

        if (format == m_pixel)
        {
            ::memcpy(this->Data(), value, Size());
            return true;
        }
        if (m_pixel == EPixel::RGB || format == EPixel::RGB)
        {
            return FillColorFrom(format, value);
        }

        if (m_pixel == EPixel::Grey)
        {
            if (format == EPixel::RGBA) return FillAlphaFrom(format, value);
            if (format == EPixel::GA) return FillColorFrom(format, value);
        }
        if (m_pixel == EPixel::GA)
        {
            if (format == EPixel::RGBA)
            {
                auto dst = this->Data();
                auto src = (uint8_t const*)value;
                auto count = Width() * Height();
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = src[1];
                    dst[1] = src[3];
                    dst += 2;
                    src += 4;
                }
                return true;
            }
            if (format == EPixel::Grey) return FillColorFrom(format, value);
        }
        if (m_pixel == EPixel::RGBA)
        {
            if (format == EPixel::GA)
            {
                auto dst = this->Data();
                auto src = (uint8_t const*)value;
                auto count = Width() * Height();
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = dst[1] = dst[2] = src[0];
                    dst[3] = src[1];
                    dst += 4;
                    src += 2;
                }
                return true;
            }
            if (format == EPixel::Grey) return FillAlphaFrom(format, value);
        }
        return false;
    }

    inline bool Image::FillAlphaFrom(EPixel format, void const* value)
    {
        if (m_pixel == EPixel::None || format == EPixel::None) return false;

        if (format == EPixel::RGB || m_pixel == EPixel::RGB) return false;

        if (format == m_pixel && format == EPixel::Grey)
        {
            ::memcpy(this->Data(), value, Size());
            return true;
        }
        auto dst = this->Data() + ((int)m_pixel - 1);
        auto src = (uint8_t const*)value + ((int)format - 1);
        auto count = Width() * Height();
        for (size_t i = 0; i < count; ++i)
        {
            *dst = *src;
            dst += (int)m_pixel;
            src += (int)format;
        }
        return true;
    }

    inline bool Image::FillColorFrom(EPixel format, void const* value)
    {
        if (m_pixel == EPixel::None || format == EPixel::None) return false;

        if (format == m_pixel)
        {
            if (format == EPixel::RGB || format == EPixel::Grey)
            {
                ::memcpy(this->Data(), value, Size());
                return true;
            }
        }

        int a = (int)m_pixel;
        int b = (int)format;
        auto dst = this->Data();
        auto src = (uint8_t const*)value;
        auto count = Width() * Height();

        if (a > 2) // rgb, rgba
        {
            if (b > 2) // rgb, rgba
            {
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = src[0];
                    dst[1] = src[1];
                    dst[2] = src[2];
                    dst += a;
                    src += b;
                }
                return true;
            }
            else // g, ga
            {
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = dst[1] = dst[2] = src[0];
                    dst += a;
                    src += b;
                }
                return true;
            }
        }
        else
        {
            if (b > 2) // rgb, rgba
            {
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = src[1];
                    dst += a;
                    src += b;
                }
                return true;
            }
            else // g, ga
            {
                for (size_t i = 0; i < count; ++i)
                {
                    dst[0] = src[0];
                    dst += a;
                    src += b;
                }
                return true;
            }
        }
        return true;
    }
    inline void Image::SetColor(size_t x, size_t y, uint8_t byte)
    {
        auto p = Data() + (y * Width() + x) * (int)m_pixel;
        for (int i = 0; i < (int)m_pixel; ++i)
        {
            p[i] = byte;
        }
    }
    inline void Image::SetColor(size_t x, size_t y, uint8_t grey, uint8_t alpha)
    {
        //__zen_must(m_pixel == EPixel::GA);
        auto p = Data() + (y * Width() + x) * (int)m_pixel;
        p[0] = grey;
        p[1] = alpha;
    }
    inline void Image::SetColor(size_t x, size_t y, Color4b color)
    {
        //__zen_must(m_pixel == EPixel::RGBA);
        auto p = Data() + (y * Width() + x) * (int)m_pixel;
        p[0] = color.r8;
        p[1] = color.g8;
        p[2] = color.b8;
        p[3] = color.a8;
    }
    inline void Image::SetColor(size_t x, size_t y, Color3b color)
    {
        //__zen_must(m_pixel == EPixel::RGB);
        auto p = Data() + (y * Width() + x) * (int)m_pixel;
        p[0] = color.r8;
        p[1] = color.g8;
        p[2] = color.b8;
    }
} // namespace Zen
